/*
 * Copyright (c) 2011. This file is public under GPLv3 license.
 * For more information about license please refer to http://www.gnu.org/licenses/gpl.html
 */

package com.freetools.watchdog.engine;

import com.freetools.watchdog.exceptions.CommandErrorException;
import com.freetools.watchdog.model.*;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Description to be do.
 *
 * @author
 */
public class LogParser
{
    private static Logger log = Logger.getLogger(LogParser.class);

    private static void occupyPieces(IModelHolder model, IRecord record) throws IOException
    {
        DAO.storeRecord(model, record);
        for (IPiece piece : record.getPieces())
        {
            piece.markAsUsed().resetCache();
        }
        //model.getSpool().write(model.getRecordFormatter().format(model, record));

    }

    private static void pieceWorks(List<IPiece> readPieces, List<IPiece> included, long start, long end, long length)
    {
        included.clear();
        int i = readPieces.size() - 1;
        long currentBase = length;
        while (i >= 0)
        {
            IPiece p = readPieces.get(i);
            if (p.isUsed()) break;

            if (start > currentBase - p.getLength() && end < currentBase)
            {
                IPiece newPiece1 = p.copy().setTo(p.getFrom() + (start - (currentBase - p.getLength())));
                IPiece newPiece2 = p.copy().setFrom(newPiece1.getTo()).setTo(newPiece1.getTo() + (end - start));
                readPieces.add(i, newPiece2);
                if (newPiece1.getLength() > 0)
                {
                    readPieces.add(i, newPiece1);
                }
                included.add(0, newPiece2);
                currentBase -= p.getLength();
                p.setFrom(newPiece2.getTo());
            } else if (start <= currentBase - p.getLength() && end >= currentBase)
            {
                included.add(0, p);
                currentBase -= p.getLength();
            } else if (start <= currentBase - p.getLength() && end < currentBase && currentBase - p.getLength() < end)
            {
                IPiece newPiece = p.copy().setTo(p.getTo() - (currentBase - end));
                readPieces.add(i, newPiece);
                currentBase -= p.getLength();
                p.setFrom(newPiece.getTo());
                included.add(0, newPiece);
            } else if (start > currentBase - p.getLength() && end >= currentBase && currentBase > start)
            {
                IPiece newPiece = p.copy().setTo(p.getTo() - (currentBase - start));
                readPieces.add(i, newPiece);
                currentBase -= p.getLength();
                p.setFrom(newPiece.getTo());
                included.add(0, p);
            } else
            {
                currentBase -= p.getLength();
            }

            i--;
        }
    }


    public static void perform(IModelHolder model) throws CommandErrorException
    {
        Pattern pattern = Pattern.compile(model.getParsePattern(), Pattern.MULTILINE | Pattern.CASE_INSENSITIVE | Pattern.DOTALL);

        List<IPiece> readPieces = model.getReadPieces();

        try
        {
            IRecord candidateRecord = null;
            IPiece currentPiece;
            int recordsFound = 0;
            for (ISource source : model.getSources())
            {
                UserInformation.info(String.format("File: %s", source.toString()));
                readPieces.add(new Piece(source, 0, 0));
                while (true)
                {
                    IPiece lastPiece = readPieces.get(readPieces.size() - 1);
                    currentPiece = lastPiece.copy().setFrom(lastPiece.getTo());
                    currentPiece.setTo(currentPiece.getTo() + currentPiece.getSource().getRecommendedBufferSize());
                    if (currentPiece.getCachedValue().length() == 0)
                        break;
                    candidateRecord = null;
                    readPieces.add(currentPiece);
                    StringBuilder content = new StringBuilder(currentPiece.getCachedValue());

                    int i = readPieces.size() - 1;
                    while (i > 0)
                    {
                        i--;
                        IPiece piece = readPieces.get(i);

                        if (piece.isUsed())
                        {
                            readPieces.remove(i);
                        } else
                        {
                            try
                            {
                                content.insert(0, piece.getCachedValue());
                            } catch (OutOfMemoryError e)
                            {
                                if (recordsFound == 0)
                                {
                                    UserInformation.info("Memory is not enough and no records found :(\n Try to use another pattern in command -parse");
                                } else
                                {
                                    UserInformation.info("Watch dog need more memory. Try to use JVM parameter -Xmx:1024m\n");
                                }
                                throw e;
                            }
                        }

                    }

                    Matcher matcher = pattern.matcher(content);
                    while (matcher.find())
                    {
                        int matcherStart = matcher.start();
                        int matcherEnd = matcher.end();
                        if (candidateRecord != null && candidateRecord.getPieces().size() > 0)
                        {
                            recordsFound++;
                            occupyPieces(model, candidateRecord);
                        }
                        candidateRecord = new Record();

                        for (int j = 0; j < model.getColumns().size(); j++)
                        {
                            IColumn column = model.getColumns().get(j);
                            if (j + 1 <= matcher.groupCount())
                            {
                                candidateRecord.setValue(parseToValue(matcher.group(j + 1), column), column);
                            }
                        }

                        pieceWorks(readPieces, candidateRecord.getPieces(), matcherStart, matcherEnd, content.length());
                    }
//                    System.out.println(String.format("Free memory: %d; Records: %d",
//                            Runtime.getRuntime().freeMemory(),
//                            model.getRecords().size()));

                }
                UserInformation.info("Found records:" + recordsFound);
            }
            if (candidateRecord != null && candidateRecord.getPieces().size() > 0)
            {
                recordsFound++;
                occupyPieces(model, candidateRecord);
            }
            UserInformation.info("Summary count of records:" + recordsFound);
        } catch (IOException e)
        {
            log.error(e);
        }
    }

    private static Object parseToValue(String text, IColumn column) throws CommandErrorException
    {
        if ("timestamp".equalsIgnoreCase(column.getType()))
        {
            try
            {
                return new Timestamp(new SimpleDateFormat(column.getFormat()).parse(text).getTime());
            } catch (ParseException e)
            {
                throw new CommandErrorException("Cannot convert to date value text: " + text +
                        "\n Date pattern: " + column.getFormat(), e);
            }
        } else if ("bigint".equalsIgnoreCase(column.getType()))
        {
            try
            {
                return Long.parseLong(text);
            } catch (NumberFormatException e)
            {
                throw new CommandErrorException("Cannot convert to long value text: " + text, e);
            }
        } else
        {
            return text;
        }

    }
}
